index.tsx (3676B)
1 import { Box } from '@mui/material'; 2 import { addDays, compareAsc, subDays } from 'date-fns'; 3 4 import Head from 'client/components/Head'; 5 import Layout from 'client/components/Layout'; 6 import SlideFooter from 'client/components/SlideFooter'; 7 import DailyView from 'client/features/daily'; 8 import { CLASSES, GRADES } from 'common/constant'; 9 import formatDate from 'common/utils/formatDate'; 10 import { zodClass, zodGrade } from 'common/utils/zod'; 11 import { caller } from 'server/routers/_app'; 12 13 import type { DayTimetable } from 'client/features/daily/types'; 14 import type { Class, Grade } from 'common/types'; 15 import type { GetStaticPaths, GetStaticProps, NextPage } from 'next'; 16 17 type UrlQuery = { 18 class: string; 19 }; 20 21 type PageProps = { 22 grade: Grade; 23 class: Class; 24 calendar: [string, DayTimetable][]; 25 }; 26 27 export const getStaticPaths: GetStaticPaths<UrlQuery> = async () => { 28 const paths = GRADES.flatMap((g) => 29 CLASSES.map((c) => ({ params: { class: `${g}-${c}` } })), 30 ); 31 32 return { 33 paths, 34 fallback: false, 35 }; 36 }; 37 38 export const getStaticProps: GetStaticProps<PageProps, UrlQuery> = async ({ 39 params, 40 }) => { 41 if (typeof params === 'undefined') { 42 return { 43 notFound: true, 44 }; 45 } 46 47 // 学年、クラスの数字が問題ないか確認 48 const [gradeNumRaw, classNumRaw] = params.class 49 .split('-') 50 .map((v) => parseInt(v)); 51 const gradeParsed = zodGrade.safeParse(gradeNumRaw); 52 const classParsed = zodClass.safeParse(classNumRaw); 53 if (!gradeParsed.success || !classParsed.success) { 54 return { 55 notFound: true, 56 }; 57 } 58 const gradeNumber = gradeParsed.data; 59 const classNumber = classParsed.data; 60 61 const now = new Date(); 62 const startDate = subDays(now, 5); 63 const endDate = addDays(now, 30); 64 65 // クラスの標準時間割表を取得 66 const { standardTimetable } = await caller.classStandard.byGradeAndClass({ 67 grade: gradeNumber, 68 class: classNumber, 69 }); 70 71 // 学年の曜日時限時間割表を取得 72 const dayPeriodCalendar = await caller.dayPeriodSchedule.byGradeAndPeriod({ 73 grade: gradeNumber, 74 startDate: formatDate(startDate), 75 endDate: formatDate(endDate), 76 }); 77 78 // クラスの臨時変更を取得 79 // 現在、臨時変更は扱っていない 80 81 // 曜日時限時間割表にクラスの科目データを当てはめていく 82 const dayTimetableCalendar = dayPeriodCalendar.map( 83 ([date, { event, ...sch }]): [string, DayTimetable] => { 84 const periods = Object.entries(sch).map(([key, value]) => [ 85 key, 86 { 87 // 曜日時限からクラスの科目データを返す 88 courses: standardTimetable[value], 89 basis: { type: 'dayPeriod', dayPeriod: value }, 90 }, 91 ]); 92 // FIXME: コード汚すぎるので修正する 93 return [ 94 date, 95 event 96 ? { 97 date, 98 event, 99 ...Object.fromEntries(periods), 100 } 101 : { 102 date, 103 ...Object.fromEntries(periods), 104 }, 105 ]; 106 }, 107 ); 108 109 // 日付順に並び替える 110 const sorted = dayTimetableCalendar.toSorted(([, a], [, b]) => 111 compareAsc(a.date, b.date), 112 ); 113 114 return { 115 props: { 116 grade: gradeNumber, 117 class: classNumber, 118 calendar: sorted, 119 }, 120 revalidate: 60 * 60, // 1時間毎の訪問で再生成 121 }; 122 }; 123 124 // 5日前~30日後の時間割を表示するページ 125 const Page: NextPage<PageProps> = (props) => { 126 const className = `${props.grade}年${props.class}組`; 127 return ( 128 <> 129 <Head title={className} /> 130 <Layout title={className} footer={<SlideFooter />}> 131 <Box width={1}> 132 {props.calendar.length ? ( 133 <DailyView {...props} /> 134 ) : ( 135 <Box px={2}> 136 <p>データがありません</p> 137 </Box> 138 )} 139 </Box> 140 </Layout> 141 </> 142 ); 143 }; 144 145 export default Page;